/*
**  Copyright (c) 2014 Michael Liu(michael.liu.point@gmail.com).
**
**  Project: Gateway Unified Management Platform
**  File:    dpif.c
**  Author:  Michael
**  Date:    06/24/2014
**
**  Purpose:
**    data path interface methods.
*/

/* Include files. */

#include "dpmgr.h"
#include "osdep.h"
#include "pfdep.h"
#include "brifopt.h"
#include "bondifopt.h"
#include "vlanifopt.h"
#include "wlanapifopt.h"
#include "wlancliifopt.h"
#include "etherifopt.h"
#include "pppoeifopt.h"
#include "pptpifopt.h"
#include "modemifopt.h"

/* Macro constant definitions. */

#define DP_IF_METHOD_ARG_MAX	(8)

/* Type definitions. */

/* Local function declarations. */

static e_ret dp_if_prepare(tdp_if * dp_if);

/* Macro API definitions. */

/* Global variable declarations. */

/* init all interfaces/devices/rules. */

e_ret dp_ifs_init(void *dp)
{
	e_ret ret;
	tdp_if *dp_if;
	struct list_head *each;
	tdata_paths *data_paths;

	data_paths = (tdata_paths *) dp;

	list_for_each(each, &data_paths->all_ifs) {
		dp_if = list_entry(each, tdp_if, all_list);
		dp_if->data_paths = data_paths;

		ret = dp_if_prepare(dp_if);
		if (ret != eRET_SUCCESS) {
			sysmgr_err("dp_if_prepare() failed, ret: %d.\n", ret);
			return (ret);
		}
	}

	return (eRET_SUCCESS);

}

e_ret dp_if_prepare(tdp_if * dp_if)
{

	e_ret ret;
	tdp_dev *dp_dev;

	ASSERT(dp_if != NULL);

	if (dp_if->prepare == eBOOL_TRUE) {
		sysmgr_dbg("data path interface [%s] already prepared.\n", dp_if->name);
		return (eRET_SUCCESS);
	}

	sysmgr_dbg("data path interface [%s] prepare.\n", dp_if->name);

	switch (dp_if->type) {
	case eDP_IF_TYPE_PPP_PPTP:
		ret = ppp_pptp_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_PPP_PPPOE:
		ret = ppp_pppoe_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_PPP_MODEM:
		ret = ppp_modem_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_BRIDGE:
		ret = bridge_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_VLAN:
		ret = vlan_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_ETH:
		ret = ether_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_WLAN24G_AP:
		ret = wlan24g_ap_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_WLAN24G_CLI:
		ret = wlan24g_cli_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_WLAN5G_AP:
		ret = wlan5g_ap_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_WLAN5G_CLI:
		ret = wlan5g_cli_if_prepare(dp_if);
		break;

	case eDP_IF_TYPE_BONDING:
		ret = bond_if_prepare(dp_if);
		break;

	default:
		ret = eRET_INVALID_ARG;
		break;
	}

	if (ret != eRET_SUCCESS) {
		sysmgr_err("data path interface [%s] type[%d] "
			   "prepare failed, ret: %d.\n", dp_if->name, dp_if->type, ret);
		return (ret);
	}

	dp_dev = dp_if->based_on_dev;
	if ((dp_dev != NULL) && (dp_dev->add_ref_if != NULL)) {
		ret = dp_dev->add_ref_if(dp_dev, dp_if);
		if (ret != eRET_SUCCESS) {
			return (ret);
		}
	}

	return (eRET_SUCCESS);

}

e_ret dp_if_do_cmd(tdp_if * dp_if, unsigned char method, ...)
{

	int n;
	e_ret ret = eRET_SUCCESS;
	void *argv[DP_IF_METHOD_ARG_MAX] = { 0 };
	va_list args;

	n = 0;
	va_start(args, method);
	do {
		if (n >= DP_IF_METHOD_ARG_MAX) {
			return (eRET_INVALID_ARG);
		} else {
			argv[n] = va_arg(args, void *);
		}
	} while (argv[n++] != NULL);
	va_end(args);

#define do_cmd(dp_if, cmd, arg...) \
({																			\
	if (dp_if->cmd == NULL) {												\
		sysmgr_err("data path interface[%s] has no [%s] capability.\n",		\
			(dp_if)->name, #cmd);											\
		ret = eRET_SUCCESS; 												\
	} else {																\
		ret = (dp_if)->cmd((dp_if), ##arg); 								\
		if (ret != eRET_SUCCESS) {											\
			sysmgr_err("data path interface[%s] do [%s] failed, ret: %d.\n",\
				(dp_if)->name, #cmd, ret);									\
			break;															\
		}																	\
	}																		\
	ret;																	\
})

#define do_cmd0(dp_if, cmd) do_cmd(dp_if, cmd)
#define do_cmd1(dp_if, cmd) do_cmd(dp_if, cmd, argv[0])
#define do_cmd2(dp_if, cmd) do_cmd(dp_if, cmd, argv[0], argv[1])
#define do_cmd3(dp_if, cmd) do_cmd(dp_if, cmd, argv[0], argv[1], argv[2])

	switch (method) {
	case eDP_IF_METHOD_CREATE:
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_CREATED) {
			do_cmd0(dp_if, create);
			dp_if->linkstatus = eDP_IF_LINK_STATUS_CREATED;
		} else {
			sysmgr_info("dp interface[%s] is already created.\n", dp_if->name);
		}
		break;

	case eDP_IF_METHOD_ENABLE:
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_CREATED) {
			do_cmd0(dp_if, create);
		}
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_ENABLED) {
			do_cmd0(dp_if, enable);
			dp_if->linkstatus = eDP_IF_LINK_STATUS_ENABLED;
		} else {
			sysmgr_info("dp interface[%s] is already enabled.\n", dp_if->name);
		}
		break;

	case eDP_IF_METHOD_LINK:
		if (!mask_test_bit(dp_if->attr, eDP_IF_ATTR_LINK)) {
			sysmgr_info("dp interface[%s] do NOT has LINK attribute.\n", dp_if->name);
			return (eRET_INVALID_STATE);
		}

		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_CREATED) {
			do_cmd0(dp_if, create);
		}
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_ENABLED) {
			do_cmd0(dp_if, enable);
		}
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_UP) {
			do_cmd0(dp_if, link);
			dp_if->linkstatus = eDP_IF_LINK_STATUS_UP;
		} else {
			sysmgr_info("dp interface[%s] is already linked.\n", dp_if->name);
		}
		break;

	case eDP_IF_METHOD_CONNECT:
		if (!mask_test_bit(dp_if->attr, eDP_IF_ATTR_NET)) {
			sysmgr_info("dp interface[%s] do NOT has NET attribute.\n", dp_if->name);
			return (eRET_INVALID_STATE);
		}

		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_CREATED) {
			do_cmd0(dp_if, create);
		}
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_ENABLED) {
			do_cmd0(dp_if, enable);
		}
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_UP) {
			do_cmd0(dp_if, link);
		}
		/* Check status in sub-function for Update config. */
		do_cmd0(dp_if, connect);
		dp_if->connstatus = eDP_IF_NET_STATUS_CONNECTED;
		break;

	case eDP_IF_METHOD_SET_LINKCFG:
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_CREATED) {
			do_cmd0(dp_if, create);
		}
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_ENABLED) {
			do_cmd0(dp_if, enable);
		}
		do_cmd2(dp_if, setlinkcfg);
		dp_if->linkstatus = eDP_IF_LINK_STATUS_DOWN;
		ret = dp_if_do_cmd(dp_if, eDP_IF_METHOD_LINK, NULL);
		if (dp_if->connstatus >= eDP_IF_NET_STATUS_CONNECTED) {
			do_cmd0(dp_if, connect);
		}
		break;

	case eDP_IF_METHOD_GET_LINKCFG:
		do_cmd3(dp_if, getlinkcfg);
		break;

	case eDP_IF_METHOD_GET_LINK_STATUS:
		do_cmd3(dp_if, getlinkstatus);
		break;

	case eDP_IF_METHOD_GET_LINKSTAT:
		do_cmd3(dp_if, getlinkstat);
		break;

	case eDP_IF_METHOD_COLLECT_LINKSTAT:
		do_cmd0(dp_if, collectlinkstat);
		break;

	case eDP_IF_METHOD_COLLECT_NETSTAT:
		do_cmd0(dp_if, collectnetstat);
		break;

	case eDP_IF_METHOD_SET_NETCFG:
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_CREATED) {
			do_cmd0(dp_if, create);
		}
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_ENABLED) {
			do_cmd0(dp_if, enable);
		}
		if (dp_if->linkstatus < eDP_IF_LINK_STATUS_UP) {
			do_cmd0(dp_if, link);
		}
		do_cmd2(dp_if, setnetcfg);
		ret = dp_if_do_cmd(dp_if, eDP_IF_METHOD_CONNECT, NULL);
		break;

	case eDP_IF_METHOD_GET_NETCFG:
		do_cmd3(dp_if, getnetcfg);
		break;

	case eDP_IF_METHOD_GET_NETSTAT:
		do_cmd3(dp_if, getnetstat);
		break;

	case eDP_IF_METHOD_GET_NET_STATUS:
		do_cmd3(dp_if, getnetstatus);
		break;

	case eDP_IF_METHOD_DISCONNECT:
		if (dp_if->connstatus > eDP_IF_NET_STATUS_DISCONNECTED) {
			do_cmd0(dp_if, disconnect);
			dp_if->connstatus = eDP_IF_NET_STATUS_DISCONNECTED;
		} else {
			sysmgr_info("dp interface[%s] is already disconnected.\n", dp_if->name);
		}
		break;

	case eDP_IF_METHOD_UNLINK:
		if (dp_if->connstatus > eDP_IF_NET_STATUS_DISCONNECTED) {
			do_cmd0(dp_if, disconnect);
			dp_if->connstatus = eDP_IF_NET_STATUS_DISCONNECTED;
		}
		if (dp_if->linkstatus > eDP_IF_LINK_STATUS_DOWN) {
			do_cmd0(dp_if, unlink);
			dp_if->linkstatus = eDP_IF_LINK_STATUS_DOWN;
		} else {
			sysmgr_info("dp interface[%s] is already unlinked.\n", dp_if->name);
		}
		break;

	case eDP_IF_METHOD_DISABLE:
		if (dp_if->connstatus > eDP_IF_NET_STATUS_DISCONNECTED) {
			do_cmd0(dp_if, disconnect);
			dp_if->connstatus = eDP_IF_NET_STATUS_DISCONNECTED;
		}
		if (dp_if->linkstatus > eDP_IF_LINK_STATUS_DOWN) {
			do_cmd0(dp_if, unlink);
		}
		if (dp_if->linkstatus > eDP_IF_LINK_STATUS_CREATED) {
			do_cmd0(dp_if, disable);
			dp_if->linkstatus = eDP_IF_LINK_STATUS_CREATED;
		} else {
			sysmgr_info("dp interface[%s] is already disabled.\n", dp_if->name);
		}
		break;

	case eDP_IF_METHOD_DESTORY:
		if (dp_if->connstatus > eDP_IF_NET_STATUS_DISCONNECTED) {
			do_cmd0(dp_if, disconnect);
			dp_if->connstatus = eDP_IF_NET_STATUS_DISCONNECTED;
		}
		if (dp_if->linkstatus > eDP_IF_LINK_STATUS_DOWN) {
			do_cmd0(dp_if, unlink);
		}
		if (dp_if->linkstatus > eDP_IF_LINK_STATUS_CREATED) {
			do_cmd0(dp_if, disable);
		}

		if (dp_if->linkstatus > eDP_IF_LINK_STATUS_NONE) {
			if ((dp_if->data_paths != NULL) && (dp_if->data_if != NULL) &&
			    (dp_if->data_if->type == eDP_IF_TYPE_BRIDGE)) {
				bridge_remove_member_if(dp_if->data_paths, dp_if->data_if, dp_if);
			}
			do_cmd0(dp_if, destroy);
			dp_if->linkstatus = eDP_IF_LINK_STATUS_NONE;
		} else {
			sysmgr_info("dp interface[%s] is already destoried.\n", dp_if->name);
		}
		break;

	default:
		break;
	}

#undef do_cmd
#undef do_cmd0
#undef do_cmd1
#undef do_cmd2
#undef do_cmd3

	return (ret);
}

e_ret dp_if_proc_req(tdata_paths * dp, tcm_dp_req_body * req, tcm_dp_rsp_body ** rsp, int *size)
{
	e_ret ret = eRET_SUCCESS;
	int rsp_size;
	int get_size;
	void *stuff_for_get;
	tmask opt_mask;
	unsigned char method;
	void *req_args;
	tdp_if *dp_if;
	tcm_dp_rsp_body *new_rsp;
	tcm_dp_if_req_body *if_req;
	tcm_dp_if_rsp_body *if_rsp;
	struct list_head *each;

	sysmgr_trace_enter();

	rsp_size = 0;
	new_rsp = NULL;

	if_req = (tcm_dp_if_req_body *) req->body;

	/* Look up data path interface entity. */
	dp_if = NULL;
	list_for_each(each, &dp->all_ifs) {
		dp_if = list_entry(each, tdp_if, all_list);
		if (strcmp(dp_if->name, if_req->name) == 0) {
			break;
		} else {
			dp_if = NULL;
		}
	}

	if (dp_if == NULL) {
		sysmgr_err("No such dp interface[%s].\n", if_req->name);
		ret = eRET_NOT_EXIST;
		goto out;
	}

	method = if_req->opt;
	opt_mask = if_req->mask;
	req_args = (void *)if_req->body;

	get_size = 0;
	stuff_for_get = NULL;

	switch (method) {
	case eDP_IF_METHOD_CREATE:
	case eDP_IF_METHOD_ENABLE:
	case eDP_IF_METHOD_DISABLE:
	case eDP_IF_METHOD_DESTORY:
	case eDP_IF_METHOD_LINK:
	case eDP_IF_METHOD_UNLINK:
	case eDP_IF_METHOD_COLLECT_LINKSTAT:
	case eDP_IF_METHOD_CONNECT:
	case eDP_IF_METHOD_DISCONNECT:
	case eDP_IF_METHOD_COLLECT_NETSTAT:
		ret = dp_if_do_cmd(dp_if, method, NULL);
		break;

	case eDP_IF_METHOD_GET_LINKCFG:
	case eDP_IF_METHOD_GET_LINKSTAT:
	case eDP_IF_METHOD_GET_LINK_STATUS:
	case eDP_IF_METHOD_GET_NETCFG:
	case eDP_IF_METHOD_GET_NETSTAT:
	case eDP_IF_METHOD_GET_NET_STATUS:
		ret = dp_if_do_cmd(dp_if, method, &stuff_for_get, &get_size, &opt_mask, NULL);
		break;

	case eDP_IF_METHOD_SET_LINKCFG:
	case eDP_IF_METHOD_SET_NETCFG:
		ret = dp_if_do_cmd(dp_if, method, req_args, &opt_mask, NULL);
		break;

	default:
		break;
	}

	rsp_size = sizeof(*new_rsp) + sizeof(*if_rsp) + get_size;
	new_rsp = malloc(rsp_size);
	if (new_rsp == NULL) {
		sysmgr_err("malloc(%d) failed, err: %d,[%s]\n", rsp_size, errno, strerror(errno));
		ret = eRET_NO_RESOURCE;
		goto out;
	}

	if_rsp = (tcm_dp_if_rsp_body *) new_rsp->body;
	memcpy(if_rsp->name, if_req->name, sizeof(if_rsp->name));
	if_rsp->opt = if_req->opt;
	if_rsp->mask = opt_mask;
	if (stuff_for_get != NULL) {
		memcpy(if_rsp->body, stuff_for_get, get_size);
		free(stuff_for_get);
	}

out:
	*size = rsp_size;
	*rsp = new_rsp;

	sysmgr_trace_exit();
	return (ret);
}
